package de.westranger.forex.trading.genetic.allel.fitness;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import de.westranger.forex.trading.GlobalConfig;
import de.westranger.forex.trading.genetic.ContentResolver;
import de.westranger.forex.trading.genetic.EvaluateOperator;
import de.westranger.forex.trading.genetic.allel.Node;
import de.westranger.forex.trading.genetic.allel.chromosom.Chromosom;
import de.westranger.forex.trading.genetic.allel.chromosom.CompositeChromosom;
import de.westranger.forex.trading.genetic.allel.operator.TerminalSymbol;
import de.westranger.forex.trading.genetic.allel.operator.UnknownOperatorException;
import de.westranger.forex.trading.genetic.allel.operator.Util;

public class MoneyManagedFitness implements ComputeFitness {
	public static boolean OUTPUT_DEBUG = false;
	
	private final ContentResolver conRes;
	private final int valueCount;
	private final EvaluateOperator eval;
	private final SignalType[] signal;
	private final double percentage;
	private final double money;
	private double stopLoss;
	private final double leverage;
	private double takeProfit;

	private final Calendar cal;
	
	public MoneyManagedFitness(final ContentResolver conRes,final double money,final double percentage,final double leverage){
		this.conRes = conRes;
		
		this.valueCount = conRes.getValueCount();
		this.eval = new EvaluateOperator(conRes);
		this.signal = new SignalType[this.valueCount];	
		this.money = money;
		this.percentage = percentage;
		
		this.leverage = leverage;
		
		this.cal = Calendar.getInstance();
	}

	@Override
	public CompositeFitness computeFitness(Chromosom chrom) throws UnknownOperatorException, ParseException{
		CompositeFitness comFit = null;
		if(chrom instanceof CompositeChromosom){
			this.takeProfit = ((CompositeChromosom) chrom).getTakeProfit();
			this.stopLoss = ((CompositeChromosom) chrom).getStoploss();
			comFit = this.computeFitness(((CompositeChromosom) chrom).getNodeSell(), ((CompositeChromosom) chrom).getNodeBuy());
		}
		return comFit;
	}

	public CompositeFitness computeFitness(final Node nodeSell,final Node nodeBuy) throws UnknownOperatorException, ParseException{
		ProfitManager proMgr = new ProfitManager(conRes, this.leverage, this.percentage, this.stopLoss, this.money,this.takeProfit);
		
		BufferedWriter bw = null;
		if(OUTPUT_DEBUG){
			try {
				bw = new BufferedWriter(new FileWriter("C:\\mql_simulated_signal_eval.csv"));
				bw.append("TIME;LOGIC_BUY;LOGIC_SELL;SIGNAL;BALANCE");
			} catch (IOException e) {
				e.printStackTrace();
			}			
		}


		this.eval.setTime(0);
		signal[0] = calcSignal(this.eval.evaluate(nodeBuy),this.eval.evaluate(nodeSell),SignalType.NOTHING);

		// hier wird noch eine laufzeit optimierung vorgenommen, wenn schon ein
		// signal existiert hat wird auch nur geschaut ob diese signal weiterhin
		// da ist, sonst wird die "konkurrenz berechnet, dies paar theoretich
		// 50% der laufzeit

		boolean buy,sell;
		for(int i=1;i<this.valueCount;i++){
			this.eval.setTime(i);
			buy = this.eval.evaluate(nodeBuy);
			sell = this.eval.evaluate(nodeSell);

			signal[i] = calcSignal(buy,sell,signal[i-1]);
		}

		for(int i=0;i<this.valueCount;i++){
			cal.setTimeInMillis((long)conRes.resolve(TerminalSymbol.TIME.getValue(), i));
			final int hour = cal.get(Calendar.HOUR_OF_DAY);
			if(hour < GlobalConfig.TRADING_START_HOUR && hour >= GlobalConfig.TRADING_END_HOUR){
				signal[i] = SignalType.NOTHING;
			}
		}
		
		
		double profit = this.money;
		int transStart = this.signal[0] != SignalType.NOTHING ? 0 : -1;
		double posTrade = 0;
		double negTrade = 0;
		double avgNegProfit = 0.0;
		double avgPosProfit = 0.0;
		double tmp = 0.0;

		//System.out.println("sig len" +signal.length);
		double counterBuy = 0.0;
		double counterSell = 0.0;
		
		for(int i=1;i<this.signal.length;i++){
			this.eval.setTime(i);
			if(OUTPUT_DEBUG){
				try {
					bw.append(conRes.resolve(TerminalSymbol.TIME.getValue(), i)	+ ";" +
							this.eval.evaluate(nodeBuy) + ";"
							+ this.eval.evaluate(nodeSell) + ";"
							+ signal[i] + ";"
							+ proMgr.getTotalProfit() + "\n");
				} catch (IOException e) {
					e.printStackTrace();
				}				
			}

			
			if(signal[i] != signal[i-1]){
				//System.out.println("\n + " + i +" "+signal[i-1] + " " + signal[i]);
				if(signal[i-1] == SignalType.BUY){
					//System.out.print("buy von " + transStart  +" bis " + i);
					tmp = proMgr.pendingBuyOrder(transStart, i);
					if(tmp != 0.0){
						counterBuy++;
						profit += tmp;
					}
				}else if(signal[i-1] == SignalType.SELL){
					//System.out.print("sell von " + transStart  +" bis " + i);
					tmp = proMgr.pendingSellOrder(transStart, i);
					if(tmp != 0.0){
						counterSell++;
						profit += tmp;
					}
				}else{
					tmp = 0.0;
				}
				
				
				if(profit < 0.000001){ // is this possible ?
					profit = 0.000001;
					break; // this is it, we dont have any money left
				}

				if(tmp > 0.0){
					posTrade++;
					avgPosProfit += tmp;
				}else if (tmp < 0.0){
					negTrade++;
					avgNegProfit += tmp;
				}

				transStart = this.signal[i] != SignalType.NOTHING ? i : -1;
				//System.out.println(" # " + tmp + "\t" + profit + "\t" + posTrade + "\t" + negTrade);
			}
			
		}

		if(OUTPUT_DEBUG){
			try {
				bw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//profit -= this.money;
		
		if(profit <= 0.000001){
			profit = 0.000001;
		}
		
		avgPosProfit /= (double)posTrade;
		avgNegProfit /= (double)negTrade;

		
		// now we will penalize everything which ses more than x different terminal symbols
		final double maxTerminal = 3;
		boolean fail = false;
		final List<Node> queryNodesBuy = Util.getQueryNodes(nodeBuy,Util.QUERY_TERMINALS);
		final Map<Integer,Integer> map = new TreeMap<Integer,Integer>();
		for(Node n:queryNodesBuy){
			map.put(n.getValue(), n.getValue());
		}		
		if(map.size() > maxTerminal){
			fail = true;
		}
		
		if(!fail){
			final List<Node> queryNodesSell = Util.getQueryNodes(nodeSell,Util.QUERY_TERMINALS);
			map.clear();
			
			for(Node n:queryNodesSell){
				map.put(n.getValue(), n.getValue());
			}

			if(map.size() > maxTerminal){
				fail = true;
			}
		}
		
		// we penalize everything which does not trade
		if (Double.isNaN(avgPosProfit) || Double.isNaN(avgPosProfit) || (posTrade == 0 && negTrade == 0) || posTrade + negTrade < 20 || fail) {
			profit = 0.000001;
			avgPosProfit = 0.0;
			avgNegProfit = 0.0;
		}
		return new CompositeFitness(profit,avgPosProfit,avgNegProfit,posTrade,negTrade,proMgr.getCounterStopLoss(),proMgr.getCounterTakeProfit(),counterBuy,counterSell);
	}

	
	private SignalType calcSignal(final boolean sigBuy, final boolean sigSell,final SignalType sigPrev) {
		SignalType result;
		
		if(sigBuy){
			result = SignalType.BUY;
		}else{
			result = SignalType.NOTHING;
		}
		
//		if (sigSell) {
//			if (sigBuy) {
//				result = sigPrev;
//			} else {
//				result = SignalType.SELL;
//			}
//		} else if (sigBuy) {
//			result = SignalType.BUY;
//		} else{
//			result = SignalType.NOTHING;
//		}

		return result;
	}
}
